今天要來聊聊的是大家生活中很常見的 Adapter 配接器模式,請大家先看 VCR,噢,是定義。
將類別的介面轉換成外界所預期的另一種介面,讓原先囿於介面不相容問題而無法協力合作的類別能夠兜在一起用。
畫個重點,「轉換成外界所預期的另一種介面」、「介面不相容問題」、「能夠兜在一起」。
家裏有一台還算不錯的音響,有 Bass 喇叭和兩個喇叭,之前聽音樂或者是看電影都有拿來用,一個小型家庭劇院的感覺,恩,挺享受的。
不過前陣子忙碌,音響好一陣子沒用,而且麻煩的是,現在在用的電腦只剩下 USB Type-C 插槽,而我的音響是 3.5mm 的接口。囧,這該如何是好⋯⋯
好險,有種產品叫 Type-C 轉 3.5mm 轉接線,我的音響又復活了!
什麼?!你說直接找 藍芽接收器 來用以後更方便?!
偶爾我們會遇到像上面這個故事的情境,手邊有兩種物品,我們需要讓它們可以兜在一起運作,然而偏偏這兩樣物品沒有可以直接組合在一起的接口。為了解決這個問題,我們可以選擇,
我們也很容易在開發和維護軟體的過程中遇到像是這種情境,特別是當我們持續的增加功能、調整商業邏輯時,我們也許需要重新設計商業邏輯層以及使用新的介面,但是先前所開發好的類別只是介面不同而已;或者是其他同事已經實作了同樣功能的類別,但是提供的公開介面卻跟我們預期的一樣。
這個時候,我們可以利用 Adapter Pattern 來減少額外的開發以及盡可能倚賴已經實作、測試過且穩定的類別。(如果已經存在的類別沒測試,那⋯⋯可能先考慮幫他加一下喔~)
所以,到底為什麼 Adapter Pattern 可以幫助我們解決這種介面不同的問題呢?這個模式的核心運作原理主要是
物件配接器的設計是使用組合物件的原則(Object Composition Principle)來達到目的, Adapter 與主程式透過共通的介面來溝通,而 Adapter 負責將傳遞進來的訊息轉換成被包裝物件所需要的格式,並調用合適的介面。
類別配接器的設計是透過繼承(Inheritance)來達成想要的效果,與物件配接器很類似,只是這邊的 Adapter 是原有類別的一種子類別。這種方式個人覺得滿特別的,但只有部分支援多重繼承(Multiple Inheritance)的程式語言例如 Python 以及 C++ 才能使用。
我們用 Rust 來示範。
// 原有的類比音訊耳機,與新的數位音訊播放介面並不相容
struct JackHeadphone;
impl JackHeadphone {
fn analog_playback(&self, _analog_signal: &str) {
println!("Analog playback");
}
}
// 新的數位音訊耳機播放介面
trait DigitalAudioPlayback {
fn playback(&self, _digital_signal: &str);
}
// 新的數位音訊耳機播放裝置,是一種藍芽耳機。
struct BluetoothHeadphone;
impl DigitalAudioPlayback for BluetoothHeadphone {
fn playback(&self, _digital_signal: &str) {
println!("Playback digital signal through Bluetooth!");
}
}
// 藍芽訊號轉類比訊號配接器,用以讓舊有的類比音訊裝置能與新系統相容。
struct BluetoothToJackAdapter {
jack_headphone: JackHeadphone,
}
// 在 BluetoothToJackAdapter 中實作數位訊號轉類比訊號的轉換功能。
impl BluetoothToJackAdapter {
fn digital_to_analog(_digital_signal: &str) -> String {
println!("converting");
return "Converted signal".to_string()
}
}
// 讓 BluetoothToJackAdapter 能回應新的 playback 介面,並調用 JackHeadphone 的播放。
impl DigitalAudioPlayback for BluetoothToJackAdapter {
fn playback(&self, digital_signal: &str) {
let analog_signal = &BluetoothToJackAdapter::digital_to_analog(digital_signal);
self.jack_headphone.analog_playback(analog_signal);
}
}
// 新的數位音訊播放設備
fn playback_digital_audio<T: DigitalAudioPlayback>(speaker: &T) {
speaker.playback("Start sending digital signal");
}
fn main() {
// 新的數位音訊設備可以直接被使用
let bluetooth_headphone = BluetoothHeadphone {};
playback_digital_audio(&bluetooth_headphone);
// 透過 Adapter Pattern 讓原有的類比音訊設備可以相容於新的介面
let jack_headphone = JackHeadphone {};
let adapted_digital_speaker = BluetoothToJackAdapter {
jack_headphone: jack_headphone
};
playback_digital_audio(&adapted_digital_speaker);
}
定義 - 將實作體系與抽象體系分離開來,讓兩者能各自更動各自演進。
Bridge 通常是在一開始設計的時候就把程式設計成適合協作的樣子,Adapter 則比較常使用在讓不相容的類別能夠與現有的軟體協作。
Adapter 是透過加入新的介面讓原有的類別能夠與新的物件協作,而 Decorator 是在不改變介面的約束下,擴增了原有物件的行為。
在維護現有產品的時候,難免會遇到需要調整類別介面的情況,與其重寫整個類別或者是重頭實作一個新的類別,在可以接受的程式碼複雜度下,可以考慮看看是不是能夠透過 Adapter Pattern 來達成目標。
想想看,下面的圖片中,橡皮筋是不是 Adapter?
取自KKNews
Adapter
Dive into Design Pattern - Adapter
作者:Yenting